package com.github.ecbp.common.data;


import com.github.ecbp.common.api.CommonResult;
import com.github.ecbp.common.api.IErrorCode;
import com.github.ecbp.common.api.ResultCode;
import com.github.ecbp.common.constant.CacheKeyConstant;
import com.github.ecbp.common.exception.base.BusinessException;
import com.github.ecbp.common.exception.user.NotLoginException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * controller抛出异常捕捉
 */
@RestControllerAdvice(annotations = {RestController.class})
public class GlobalExceptionHandler {

    private Logger LOGGER = LoggerFactory.getLogger(getClass());

    @Autowired
    private HttpServletRequest request;

    private void judgeFeign(Exception ex) throws Exception {
        boolean ifContailsFeign = Optional.ofNullable(request)
                .map(HttpServletRequest::getRequestURI)
                .map(re -> re.contains(CacheKeyConstant.FEIGN_VERSION)).orElse(false);
        if (ifContailsFeign) {
            throw ex;
        }
    }

    /**
     * 全局异常捕捉处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult errorHandler(Exception ex) throws Exception {
        judgeFeign(ex);
        LOGGER.error(ex.getMessage(), ex);
        return CommonResult.failed();
    }

    /**
     * feign 断言异常信息拦截
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = HystrixRuntimeException.class)
    public CommonResult errorHandler(HystrixRuntimeException ex) throws Exception {
        judgeFeign(ex);
        if (ex.getCause() != null && ex.getCause() instanceof IllegalArgumentException) {
            LOGGER.error(ex.getCause().getMessage(), ex.getCause());
            return CommonResult.failed(ex.getCause().getMessage());
        }
        LOGGER.error(ex.getMessage(), ex);
        return CommonResult.failed();
    }

    /**
     * 空指针异常捕捉处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public CommonResult nullErrorHandler(NullPointerException ex) throws Exception {
        judgeFeign(ex);
        LOGGER.error(ex.getMessage(), ex);
        return CommonResult.failed(ResultCode.NULL_ERROR);
    }


    /**
     * 断言校验异常捕捉处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    public CommonResult assertErrorHandler(IllegalArgumentException ex) throws Exception {
        judgeFeign(ex);
        LOGGER.error(ex.getMessage(), ex);
        return CommonResult.failed(ResultCode.ASSERT_CHECK_ERROR);
    }

    /**
     * 验证拦截器
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    public CommonResult assertErrorHandlerWithoutJson(BindException ex) throws Exception {
        judgeFeign(ex);
        BindingResult result = ex.getBindingResult();
        if (result.hasErrors()) {
            String res = result.getFieldErrors().stream()
                    .map(a -> a.getField() + CacheKeyConstant.PATH_SEPARATOR + a.getDefaultMessage())
                    .collect(Collectors.joining(";"));
            return CommonResult.failed(ResultCode.PARAMS_ERROR, res);
        }
        return CommonResult.failed();
    }


    /**
     * 方法参数级别的校验，捕捉异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult assertErrorHandlerWithMethodParameter(ConstraintViolationException ex) throws Exception {
        judgeFeign(ex);
        Set<ConstraintViolation<?>> set = ex.getConstraintViolations();
        String message = set.stream().map(a -> a.getMessage()).reduce((a, b) -> a + "," + b).orElse("");
        return CommonResult.failed(ResultCode.PARAMS_ERROR, message);
    }

    /**
     * bean对象校验异常捕捉
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult beanValidErrorHandler(MethodArgumentNotValidException ex) throws Exception {
        judgeFeign(ex);
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        fieldErrors.forEach(e -> sb.append(e.getField()).append(CacheKeyConstant.PATH_SEPARATOR).append(e.getDefaultMessage()).append(";"));
        return CommonResult.failed(ResultCode.PARAMS_ERROR, sb.toString());
    }

    /**
     * 尚未登录异常拦截
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = NotLoginException.class)
    public CommonResult assertErrorHandler(NotLoginException ex, HttpServletResponse response, HttpServletRequest request) throws Exception {
        judgeFeign(ex);
        return CommonResult.failed(ex.getResultCode());
    }

    /**
     * 业务异常拦截
     */
    @ExceptionHandler(value = BusinessException.class)
    public CommonResult businessErrorHandler(BusinessException ex) throws Exception {
        judgeFeign(ex);
        IErrorCode c = ex.getResultCode();
        String message = c.getMessage();
        LOGGER.info(message, ex);
        return CommonResult.failed(c, message);
    }

    @ExceptionHandler(value = AuthenticationException.class)
    public CommonResult businessErrorHandler(AuthenticationException ex) {
        // TODO 未能捕获AuthenticationException异常
        return CommonResult.failed(ex.getMessage());
    }
}
